#pragma once

#include <QtGlobal>
#include <QObject>
#include <QPointF>

#include <cmath>

#if !defined(Q_QDOC) && !defined(Q_MOC_RUN)
#define L7_Q_ENUM(ENUM) \
    inline const QMetaObject *qt_getEnumMetaObject(ENUM) Q_DECL_NOEXCEPT { return &staticMetaObject; } \
    inline Q_DECL_CONSTEXPR const char *qt_getEnumName(ENUM) Q_DECL_NOEXCEPT { return #ENUM; }
#define L7_Q_FLAG(ENUM) L7_Q_ENUM(ENUM)
#else
#define L7_Q_ENUM Q_ENUM
#define L7_Q_FLAG Q_FLAG
#endif

#ifndef Q_MOC_RUN
namespace
#else
class
#endif
L7 {

#if defined(Q_MOC_RUN)
    Q_GADGET
public:
#endif
    extern const QMetaObject staticMetaObject;

    // AnchorLocation
    enum Anchor
    {
        AnchorTopLeft = 1,
        AnchorTopCenter,
        AnchorTopRight,
        AnchorRightCenter,
        AnchorBottomRight,
        AnchorBottomCenter,
        AnchorBottomLeft,
        AnchorLeftCenter,
        AnchorCenter,
    };

    enum class CornerStyle
    {
        Square = 1,
        Chamfer,
        Round
    };

    enum CornerLocation
    {
        TopLeftCorner = 0x01,
        TopRightCorner = 0x02,
        BottomRightCorner = 0x04,
        BottomLeftCorner = 0x08
    };
    Q_DECLARE_FLAGS(CornerLocations, CornerLocation)

    enum NamingScheme
    {
        Numeric = 1,
        AlphaNumeric,
        AlphaNumericRowOnly,
        AlphaNumericColumnOnly,
    };


    enum PadStackElementFunction
    {
        UnknownElementFunction = 0,
        MountedLand,
        OppositeLand,
        MountedSolderMask,
        OppositeSolderMask,
        MountedSolderPaste,
        OppositeSolderPaste,
        MountedAssembly,
        OppositeAssembly,
        InternalLand,
        PlaneLand,
        ThermalReliefLand,
        Hole,
        KeepOut
    };

    // TBD: Mounting hole can have reinforcement vias...
    enum PadStackRole
    {
        UnkownPadStackRole = 0,
        SurfaceTerminal,
        ThroughTerminal,
        Via,
        TestPoint,
        Fiducial,
        Mechanical, // Aternative naming: Mounting, Hardware, ...
    };


    enum LayerPolarity
    {
        NegativeLayer,
        PositiveLayer,
    };

    enum class LayerSide
    {
        Top,
        Bottom,
        Internal
    };

    enum class LayerFunction
    {
        PlacementKeepout = 0,
        AssemblyDrawing,
        Silkscreen,
        SolderResist,
        SolderPaste,
        Drill,
        Copper,
    };

    enum PackageTerminalStyle
    {
        TerminalBall = 0,
        TerminalBottomOnly,
        TerminalGullLead,
        TerminalInwardLLead,
        TerminalJLead,
        TerminalOutwardLLead,
        TerminalUnderLLead
    };

    enum PackageBodyStyle
    {
        BodyCeramicCan = 0,
        BodyCeramicChip,
        BodyCeramicFlat,
        BodyCeramicSubstrate,
        BodyMoldedBottom,
        BodyMoldedCenter,
        BodyMoldedFlat,
        BodyMoldedTop,
        BodyPcbSubstrate
    };

#ifndef Q_QDOC
    L7_Q_ENUM(Anchor)
    L7_Q_ENUM(CornerStyle)
    L7_Q_FLAG(CornerLocations)
    L7_Q_ENUM(NamingScheme)
    L7_Q_ENUM(PadStackElementFunction)
    L7_Q_ENUM(PadStackRole)
    L7_Q_ENUM(LayerPolarity)
    L7_Q_ENUM(LayerSide)
    L7_Q_ENUM(LayerFunction)
#endif
}
Q_DECLARE_METATYPE(L7::Anchor)
Q_DECLARE_METATYPE(L7::PadStackElementFunction)
Q_DECLARE_METATYPE(L7::PadStackRole)

Q_DECLARE_OPERATORS_FOR_FLAGS(L7::CornerLocations)

struct Transform
{
    // In application order
    // 1. Mirror the feature
    bool mirror = false;
    // 2. Rotate the feature
    qreal rotation = 0.0;
    // 3. Translate the feature
    QPointF translation = QPointF(0, 0);
};
Q_DECLARE_METATYPE(Transform)

inline bool operator == (const Transform &lhs, const Transform &rhs)
{
    return lhs.mirror == rhs.mirror &&
            qFuzzyCompare(rhs.rotation, lhs.rotation) &&
            rhs.translation == lhs.translation;
}

inline bool operator != (const Transform &lhs, const Transform &rhs)
{
    return !(lhs == rhs);
}

inline bool fuzzyCompare(double f1, double f2)
{
    if (qFuzzyIsNull(f1) && qFuzzyIsNull(f2))
        return true;
    if (qFuzzyIsNull(f1) || qFuzzyIsNull(f2))
        return false;
    return qFuzzyCompare(f1, f2);
}

inline double normaliseAngle(double angle)
{
    if (angle >= 0)
    {
        return std::fmod(angle, 360.0);
    }
    return 360.0-std::fmod(-angle, 360.0);
}

inline double normaliseAngleSpan(double span)
{
    if (span >= 0)
    {
        double a = std::fmod(span, 360.0);
        if (qFuzzyIsNull(a))
            return 360.0;
        return a;
    }
    double a = std::fmod(-span, 360.0);
    if (qFuzzyIsNull(a))
        return -360.0;
    return -a;
}
